﻿using OpenCvSharp;
using OpenCvSharp.Dnn;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms.VisualStyles;

namespace IDCardReaderCZ.Detect
{
    public class FaceDetect
    {
        public static List<System.Drawing.Point> FindFace(string image_path)
        {
            List<System.Drawing.Point> facePoints = new List<System.Drawing.Point>();
            try
            {
                if (string.IsNullOrEmpty(image_path) || !File.Exists(image_path))
                {
                    return facePoints;
                }
                
                var opencv_net = CvDnn.ReadNetFromOnnx(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location) + ".\\yolov8n-face.onnx");
                int inpWidth = 640;
                int inpHeight = 640;
                var image = new Mat(image_path);
                int newh = 0;
                int neww = 0;
                int padh = 0;
                int padw = 0;
                Mat mat = ResizeImage(image, inpHeight, inpWidth, ref newh, ref neww, ref padh, ref padw);
                float ratioh = (float)image.Rows / (float)newh;
                float ratiow = (float)image.Cols / (float)neww;
                var BN_image = CvDnn.BlobFromImage(mat, 1.0 / 255.0, new OpenCvSharp.Size(inpWidth, inpHeight), new Scalar(0.0, 0.0, 0.0), swapRB: true, crop: false);
                opencv_net.SetInput(BN_image);
                Mat[] array = new Mat[3]
                {
                new Mat(),
                new Mat(),
                new Mat()
                };
                string[] outBlobNames = opencv_net.GetUnconnectedOutLayersNames().ToArray();
                var dt1 = DateTime.Now;
                opencv_net.Forward(array, outBlobNames);
                var dt2 = DateTime.Now;
                List<Rect> list = new List<Rect>();
                List<float> list2 = new List<float>();
                List<List<OpenCvSharp.Point>> list3 = new List<List<OpenCvSharp.Point>>();
                int reg_max = 16;
                float nms_threshold = 0.5f;
                int num_class = 1;
                float score_threshold = 0.25f;
                GenerateProposal(inpHeight, inpWidth, reg_max, num_class, score_threshold, 40, 40, array[0], list, list2, list3, image.Rows, image.Cols, ratioh, ratiow, padh, padw);
                GenerateProposal(inpHeight, inpWidth, reg_max, num_class, score_threshold, 20, 20, array[1], list, list2, list3, image.Rows, image.Cols, ratioh, ratiow, padh, padw);
                GenerateProposal(inpHeight, inpWidth, reg_max, num_class, score_threshold, 80, 80, array[2], list, list2, list3, image.Rows, image.Cols, ratioh, ratiow, padh, padw);
                int[] indices = new int[list.Count];
                CvDnn.NMSBoxes(list, list2, score_threshold, nms_threshold, out indices);
                List<Rect> list4 = new List<Rect>();
                List<List<OpenCvSharp.Point>> list5 = new List<List<OpenCvSharp.Point>>();
                List<float> list6 = new List<float>();
                foreach (int index in indices)
                {
                    list4.Add(list[index]);
                    list5.Add(list3[index]);
                    list6.Add(list2[index]);
                }
                if (list4.Count > 0)
                {
                    //sb.Clear();
                    //sb.AppendLine("推理耗时:" + (dt2 - dt1).TotalMilliseconds + "ms");
                    //sb.AppendLine("--------------------------");
                    //result_image = image.Clone();
                    for (int j = 0; j < list4.Count; j++)
                    {
                        //Cv2.Rectangle(result_image, list4[j], new Scalar(0.0, 0.0, 255.0), 2);
                        //Cv2.PutText(result_image, "face-" + list6[j].ToString("0.00"), new OpenCvSharp.Point(list4[j].X, list4[j].Y - 10), HersheyFonts.HersheySimplex, 1.0, new Scalar(0.0, 0.0, 255.0), 2);
                        //foreach (OpenCvSharp.Point item in list5[j])
                        //{
                        //Cv2.Circle(result_image, item, 4, new Scalar(0.0, 255.0, 0.0), -1);
                        //}
                        //sb.AppendLine(string.Format("{0}：{1},({2},{3},{4},{5})", "face", list6[j].ToString("0.00"), list4[j].TopLeft.X, list4[j].TopLeft.Y, list4[j].BottomRight.X, list4[j].BottomRight.Y));
                        facePoints.Add(new System.Drawing.Point(list4[j].TopLeft.X, list4[j].TopLeft.Y));
                    }
                    //pictureBox2.Image = new Bitmap(result_image.ToMemoryStream(".png"));
                    //textBox1.Text = sb.ToString();
                }
                image.Release();
                return facePoints;
            }
            catch
            {
                throw;
            }
        }

        public static float sigmoid_x(float x)
        {
            return (float)(1.0 / (1.0 + Math.Exp(0f - x)));
        }

        public static void softmax_(ref float[] x, ref float[] y, int length)
        {
            float num = 0f;
            int num2 = 0;
            for (num2 = 0; num2 < length; num2++)
            {
                y[num2] = (float)Math.Exp(x[num2]);
                num += y[num2];
            }
            for (num2 = 0; num2 < length; num2++)
            {
                y[num2] /= num;
            }
        }

        public static Mat ResizeImage(Mat srcimg, int inpHeight, int inpWidth, ref int newh, ref int neww, ref int padh, ref int padw)
        {
            int rows = srcimg.Rows;
            int cols = srcimg.Cols;
            newh = inpHeight;
            neww = inpWidth;
            Mat mat = new Mat();
            if (rows != cols)
            {
                float num = (float)rows / (float)cols;
                if (num > 1f)
                {
                    newh = inpHeight;
                    neww = (int)((float)inpWidth / num);
                    Cv2.Resize(srcimg, mat, new OpenCvSharp.Size(neww, newh));
                    padw = (int)((double)(inpWidth - neww) * 0.5);
                    Cv2.CopyMakeBorder(mat, mat, 0, 0, padw, inpWidth - neww - padw, BorderTypes.Constant, new Scalar(0.0, 0.0, 0.0));
                }
                else
                {
                    newh = (int)((float)inpHeight * num);
                    neww = inpWidth;
                    Cv2.Resize(srcimg, mat, new OpenCvSharp.Size(neww, newh));
                    padh = (int)((double)(inpHeight - newh) * 0.5);
                    Cv2.CopyMakeBorder(mat, mat, padh, inpHeight - newh - padh, 0, 0, BorderTypes.Constant, new Scalar(0.0, 0.0, 0.0));
                }
            }
            else
            {
                Cv2.Resize(srcimg, mat, new OpenCvSharp.Size(neww, newh));
            }
            return mat;
        }

        public unsafe static void GenerateProposal(int inpHeight, int inpWidth, int reg_max, int num_class, float score_threshold, int feat_h, int feat_w, Mat output, List<Rect> position_boxes, List<float> confidences, List<List<OpenCvSharp.Point>> landmarks, int imgh, int imgw, float ratioh, float ratiow, int padh, int padw)
        {
            int num = (int)Math.Ceiling((double)(inpHeight / feat_h));
            int num2 = feat_h * feat_w;
            float* ptr = (float*)(void*)output.DataStart;
            float* ptr2 = ptr + num2 * reg_max * 4;
            float* ptr3 = ptr + num2 * (reg_max * 4 + num_class);
            for (int i = 0; i < feat_h; i++)
            {
                for (int j = 0; j < feat_w; j++)
                {
                    int num3 = -1;
                    float num4 = -10000f;
                    int num5 = i * feat_w + j;
                    for (int k = 0; k < num_class; k++)
                    {
                        float num6 = ptr2[k * num2 + num5];
                        if (num6 > num4)
                        {
                            num4 = num6;
                            num3 = k;
                        }
                    }
                    float num7 = sigmoid_x(num4);
                    if (!(num7 > score_threshold))
                    {
                        continue;
                    }
                    float[] array = new float[4];
                    float[] x = new float[reg_max];
                    float[] y = new float[reg_max];
                    for (int l = 0; l < 4; l++)
                    {
                        for (int m = 0; m < reg_max; m++)
                        {
                            x[m] = ptr[(l * reg_max + m) * num2 + num5];
                        }
                        softmax_(ref x, ref y, reg_max);
                        float num8 = 0f;
                        for (int n = 0; n < reg_max; n++)
                        {
                            num8 += (float)n * y[n];
                        }
                        array[l] = num8 * (float)num;
                    }
                    float num9 = ((float)j + 0.5f) * (float)num;
                    float num10 = ((float)i + 0.5f) * (float)num;
                    float num11 = Math.Max((num9 - array[0] - (float)padw) * ratiow, 0f);
                    float num12 = Math.Max((num10 - array[1] - (float)padh) * ratioh, 0f);
                    float num13 = Math.Min((num9 + array[2] - (float)padw) * ratiow, imgw - 1);
                    float num14 = Math.Min((num10 + array[3] - (float)padh) * ratioh, imgh - 1);
                    Rect item = new Rect((int)num11, (int)num12, (int)(num13 - num11), (int)(num14 - num12));
                    position_boxes.Add(item);
                    confidences.Add(num7);
                    List<OpenCvSharp.Point> list = new List<OpenCvSharp.Point>();
                    for (int num15 = 0; num15 < 5; num15++)
                    {
                        float num16 = ((ptr3[num15 * 3 * num2 + num5] * 2f + (float)j) * (float)num - (float)padw) * ratiow;
                        float num17 = ((ptr3[(num15 * 3 + 1) * num2 + num5] * 2f + (float)i) * (float)num - (float)padh) * ratioh;
                        list.Add(new OpenCvSharp.Point((int)num16, (int)num17));
                    }
                    landmarks.Add(list);
                }
            }
        }
    }
}
